// UCLA CS 111 Lab 1 command execution

#include "command.h"
#include "command-internals.h"

#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int command_status (command_t c)
{
  return c->status;
}


void exec_simple(command_t c)
{
	pid_t pid = fork();

	int status = 0;

	switch (pid)
	{
	  case -1: 
	  {
		fprintf(stderr, "ERROR: Fork failed.");
	  } 
	
	  case 0: 
	  { 	
		//Check input
		if (c->input)
		{
			int f_input = open(c->input, O_RDWR);

			dup2(f_input, 0);
			close(f_input);
		}

		//Check output
		if (c->output)
		{
			int f_output = open(c->output, O_RDWR | O_CREAT, 0666);
		
			dup2(f_output, 1);
			close(f_output);
		}

		//Execute command
		execvp(c->u.word[0], c->u.word);

		fprintf(stderr, "Command not found\n");

		//Break in case of error
		break;
	  }

	  default:
	  {
		// Save exit status
		waitpid(pid, &status, 0);
		c->status = status;
			break;
	  }

	}
}


void exec_and(command_t c)
{
	int time = 0; //TODO: Time travel

	execute_command(c->u.command[0], time);

	if (c->u.command[0]->status == 0) 
	{ 	// If && returns true
		execute_command(c->u.command[1], time);
		c->status = c->u.command[1]->status;
	}

	else 
	{	// If && was false then don't continue executing
		c->status = c->u.command[0]->status;
	}
}


void exec_or(command_t c)
{
	int time = 0; //TODO: Time travel

	execute_command(c->u.command[0], time);

	if (c->u.command[0]->status != 0) 
	{ 	// If false
		execute_command(c->u.command[1], time);
		c->status = c->u.command[1]->status;
	} 
	
	else 
	{	// If true then don't continue executing
		c->status = c->u.command[0]->status;
	}
}


void exec_subshell(command_t c)
{	
	int time = 0; //TODO: Time travel

	//Check input
	if (c->input)
	{
		int f_input = open(c->input, O_RDWR);

		dup2(f_input, 0);
		close(f_input);
	}

	//Check output
	if (c->output)
	{
		int f_output = open(c->output, O_RDWR | O_CREAT, 0666);

		dup2(f_output, 1);
		close(f_output);
	}

	//Execute subshell command
	execute_command(c->u.subshell_command, time);
	c->status = c->u.subshell_command->status;
}


void exec_pipe(command_t c)
{
	int time = 0; //TODO: Time travel
	int status = 0;

	//Make Pipe
	int pipefd[2];
	pipe(pipefd);

	pid_t pid_a = fork();

	// Do the right side
	if (pid_a == 0)
	{
		dup2(pipefd[0], 0);
		close(pipefd[0]);
		close(pipefd[1]);

		execute_command(c->u.command[1], time);
		exit(c->u.command[1]->status);
	}

	else if (pid_a == -1)
	{
		fprintf(stderr, "ERROR: Fork fail");
	}

	pid_t pid_b = fork();

	// Do the left side
	if (pid_b == 0)
	{
		dup2(pipefd[1],1);
		close(pipefd[0]);
		close(pipefd[1]);

		execute_command(c->u.command[0], time);
		exit(c->u.command[0]->status);
	}

	else if (pid_b == -1)
		fprintf(stderr, "ERROR: Fork fail");

	close(pipefd[0]);
	close(pipefd[1]);
	
	//Exit status
	waitpid(pid_a, &status, 0);
	waitpid(pid_b, &status, 0);
	c->status = status;
}


void exec_seq(command_t c)
{
	int time = 0; //TODO: Time travel

	execute_command(c->u.command[0], time);
	execute_command(c->u.command[1], time);

	c->status = c->u.command[1]->status;
}


void
execute_command (command_t c, int time_travel)
{
	if (c->type == SIMPLE_COMMAND)
		exec_simple(c);

	else if (c->type == AND_COMMAND)
		exec_and(c);

	else if (c->type == OR_COMMAND)
		exec_or(c);

	else if (c->type == SEQUENCE_COMMAND)
		exec_seq(c);

	else if (c->type == SUBSHELL_COMMAND)
		exec_subshell(c);

	else if (c->type == PIPE_COMMAND)
		exec_pipe(c);

	else
		//Should actually never get here, because exec_simple()
		//takes care of invalid commands.
		fprintf(stderr, "Error in execute_command"); 
}
